using System;
using System.Collections.Generic;
using System.Text;
using ElteIk.RoboSoccer.Core;
using System.Threading;
using ElteIk.RoboSoccer.Core.Physics;

namespace ElteIk.RoboSoccer.Communication
{
    /// <summary>
    /// Multithreaded server for local games and developing.
    /// 
    /// It constructs one thread for each team tactics module. It will consume all CPU power because we don't put the threads to sleep.
    /// (Putting the threads asleep is very inaccurate because the minimal time of sleeping is about 15 milliseconds, which is too much time.) 
    /// </summary>
    public class MultithreadedServer
    {
        #region Private fields
        private RoboSoccerGame game;

        private ITactics leftTeamClient;
        private ITactics rightTeamClient;

        private List<IRobotController> leftTeamControllerData = new List<IRobotController>();
        private List<IRobotController> rightTeamControllerData = new List<IRobotController>();

        private Thread leftTeamThread;
        private Thread rightTeamThread;
        
        #endregion

        #region Lifecycle

        /// <summary>
        /// Constructs the server from the game. Subscribes to the game's update event.
        /// </summary>
        /// <param name="game"></param>
        public MultithreadedServer(RoboSoccerGame game)
        {
            this.game = game;
            game.Updated += new EventHandler(game_Updated);
            game.GameStopped += new EventHandler(game_GameStopped);
            game.GameStarted += new EventHandler(game_GameStarted);
        }

        void game_GameStarted(object sender, EventArgs e)
        {
            if (leftTeamThread != null && !leftTeamThread.IsAlive)
                leftTeamThread.Start();
            if (rightTeamThread != null && !rightTeamThread.IsAlive)
                rightTeamThread.Start();
        }

        void game_GameStopped(object sender, EventArgs e)
        {
            if (leftTeamThread != null && leftTeamThread.ThreadState == ThreadState.Background)
            {
                leftTeamThread.Abort();                
                leftTeamThread.Join();
                leftTeamThread = null;
            }
            if (rightTeamThread != null && rightTeamThread.ThreadState == ThreadState.Background)
            {
                rightTeamThread.Abort();
                rightTeamThread.Join();
                rightTeamThread = null;
            }
        }

        /// <summary>
        /// Handling the update event. We apply the tactics controller data after the game was updated.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void game_Updated(object sender, EventArgs e)
        {
            lock (leftTeamControllerData)
            {
                for (int i = 0; i < leftTeamControllerData.Count; ++i)
                {
                    UpdateRobot(game.LeftTeam.Robots[i], leftTeamControllerData[i]);
                }
                leftTeamControllerData.Clear();
            }

            lock (rightTeamControllerData)
            {
                for (int i = 0; i < rightTeamControllerData.Count; ++i)
                {
                    UpdateRobot(game.RightTeam.Robots[i], rightTeamControllerData[i]);
                }
                rightTeamControllerData.Clear();
            }
        }

        #endregion

        #region Connecting tactics to the server

        /// <summary>
        /// Adds a tactics client to the server for the team identified by its id.
        /// </summary>
        /// <param name="tacticsClient"></param>
        /// <param name="teamId"></param>
        public void AddClient(ITactics tacticsClient, int teamId)
        {
            // Add to the left team
            if (teamId == 1)
            {
                AddClient(tacticsClient, ref leftTeamThread, LeftTeamMain, ref leftTeamClient);
                leftTeamThread.Name = "Left Team Tactics Thread";
            }
            // Else add it to the right team
            else if (teamId == 2)
            {
                AddClient(tacticsClient, ref rightTeamThread, RightTeamMain, ref rightTeamClient);
                rightTeamThread.Name = "Right Team Tactics Thread";
            }
        }

        /// <summary>
        /// Helper method for registering the tactics client.
        /// </summary>
        /// <param name="tacticsClient"></param>
        /// <param name="tacticsMainThread"></param>
        /// <param name="tacticsMainMethod"></param>
        /// <param name="localClient"></param>
        private void AddClient(ITactics tacticsClient, ref Thread tacticsMainThread, ThreadStart tacticsMainMethod, ref ITactics localClient)
        {
            // If there was a thread running earlier, stop and dispose it
            if (tacticsMainThread != null && tacticsMainThread.ThreadState == ThreadState.Background)
            {
                tacticsMainThread.Abort();
                tacticsMainThread.Join();
            }

            localClient = tacticsClient;
            localClient.Game = Clone(game);

            tacticsMainThread = new Thread(new ThreadStart(tacticsMainMethod));
            tacticsMainThread.IsBackground = true;
            //if (game.IsRunning)
            {
                tacticsMainThread.Start();
            }
        }

        /// <summary>
        /// Returns true if there is an active tactics thread with the given id.
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public bool HasClient(int id)
        {
            if (id == 1)
            {
                return leftTeamThread != null && leftTeamThread.ThreadState == ThreadState.Background;
            }
            else if (id == 2)
            {
                return rightTeamThread != null && rightTeamThread.ThreadState == ThreadState.Background;
            }
            return false;
        }

        /// <summary>
        /// Disconnects the client identified by its controlled team index.
        /// </summary>
        /// <param name="id"></param>
        public void RemoveClient(int id)
        {
            if (id == 1)
            {
                leftTeamThread.Abort();
                leftTeamThread.Join();
                leftTeamClient = null;
            }
            else if (id == 2)
            {
                rightTeamThread.Abort();
                rightTeamThread.Join();
                rightTeamClient = null;
            }
        }

        #endregion

        #region Copier and cloner functions

        /// <summary>
        /// Makes two vectors equal.
        /// </summary>
        /// <param name="source"></param>
        /// <param name="destination"></param>
        private void Copy(Vector2D source, Vector2D destination)
        {
            destination.X = source.X;
            destination.Y = source.Y;
        }

        /// <summary>
        /// Makes two physical objects equal.
        /// </summary>
        /// <param name="source"></param>
        /// <param name="destination"></param>
        private void Copy(IPhysicalObject source, IPhysicalObject destination)
        {
            Copy(source.Heading, destination.Heading);
            Copy(source.Position, destination.Position);
            Copy(source.Velocity, destination.Velocity);
            destination.Rotation = source.Rotation;
            destination.Mass = source.Mass;
        }

        /// <summary>
        /// Makes two polygons equal.
        /// </summary>
        /// <param name="source"></param>
        /// <param name="destination"></param>
        private void Copy(Polygon source, Polygon destination)
        {
            Copy(source as IPhysicalObject, destination as IPhysicalObject);
            destination.Points.Clear();
            foreach (Vector2D vector in source.Points)
            {
                destination.Points.Add(new Vector2D(vector));
            }
        }

        /// <summary>
        /// Makes two robots equal.
        /// </summary>
        /// <param name="source"></param>
        /// <param name="destination"></param>
        private void Copy(RobotPlayer source, RobotPlayer destination)
        {
            Copy(source as Polygon, destination as Polygon);
            destination.MaxPower = source.MaxPower;
            destination.MotorDistance = source.MotorDistance;
            destination.MotorStrength = source.MotorStrength;
            destination.MotorLeft = source.MotorLeft;
            destination.MotorRight = source.MotorRight;
            destination.Brake = source.Brake;
            destination.Name = source.Name;
            destination.Size = source.Size;
        }

        /// <summary>
        /// Creates a new copy of the game.
        /// </summary>
        /// <param name="game"></param>
        /// <returns></returns>
        private RoboSoccerGame Clone(RoboSoccerGame game)
        {
            RoboSoccerGame copiedGame = new RoboSoccerGame();

            // TODO: Building up the physical engine (from config file) should be optional.
            copiedGame.Initialize();

            Copy(game.Ball, copiedGame.Ball);

            return copiedGame;
        }

        #endregion

        #region Updater functions

        /// <summary>
        /// Refreshes the game data.
        /// </summary>
        /// <param name="game"></param>
        private void UpdateGameData(RoboSoccerGame game)
        {
            // Copy the ball
            Copy(this.game.Ball, game.Ball);

            // Update GameTime
            game.Time = this.game.Time;

            // And the robots
            for (int i = 0; i < this.game.Robots.Count; ++i)
            {
                Copy(this.game.Robots[i], game.Robots[i]);
            }
        }

        /// <summary>
        /// Updates the robots controller calculated by the tactics.
        /// </summary>
        /// <param name="robot"></param>
        /// <param name="controller"></param>
        private void UpdateRobot(IRobotController robot, IRobotController controller)
        {
            robot.Brake = controller.Brake;
            robot.MotorLeft = controller.MotorLeft;
            robot.MotorRight = controller.MotorRight;
        }

        /// <summary>
        /// Updates the list of robot controller datas.
        /// </summary>
        /// <param name="team"></param>
        /// <param name="controllers"></param>
        private void UpdateControllersData(RoboSoccerTeam team, List<IRobotController> controllers)
        {
            controllers.Clear();
            foreach (IRobotController robotController in team.Robots)
            {
                controllers.Add(new RobotPlayerControlData(robotController.MotorLeft, robotController.MotorRight, robotController.Brake));
            }
        }

        #endregion

        #region Main methods of the tactics threads

        private void LeftTeamMain()
        {
            while (true)
            {
                UpdateGameData(leftTeamClient.Game);
                leftTeamClient.Do();
                lock (leftTeamControllerData)
                {
                    UpdateControllersData(leftTeamClient.Game.LeftTeam, leftTeamControllerData);
                }
                Thread.Sleep(0);
            }
        }

        private void RightTeamMain()
        {
            while (true)
            {
                UpdateGameData(rightTeamClient.Game);
                rightTeamClient.Do();
                lock (rightTeamControllerData)
                {
                    UpdateControllersData(rightTeamClient.Game.RightTeam, rightTeamControllerData);
                }
                Thread.Sleep(0);
            }
        }

        #endregion
    }
}
